Computer systems typically have services (e.g., file services) that are provided to clients (e.g., an application program). The services may implement a high-level service using low-level operations. For example, a file system may provide high-level commands to open and read files, which are implemented using low-level operations, such as sector seek and sector read. To access a file, an application program can request the file system to open the file and then read the file. The file system performs the necessary low-level operations to perform the high-level commands (e.g., open and read). Thus, the developer of an application program does not need to understand or use the low-level operations, but rather can rely on the file system for performing the appropriate low-level operations. The programs that request services of a service provider are referred to as clients. To have a service performed on its behalf, a client sends a request to a service provider of that service. The service provider performs the request and returns a result such as data or status information.
The requests for services can be satisfied synchronously or asynchronously by the service provider. When a request is satisfied synchronously, the client requesting the service waits until the service completes before continuing. For example, a client may request a service by invoking a read file function of a file system that returns after the file system has retrieved the data of the file. When a request is satisfied asynchronously, the client requesting the service does not wait until the service completes before continuing. However, when the service does complete, the service provider then notifies the client. For example, a client may request a service by invoking a read file function of the file system. That function may return immediately to the client so that the client can continue executing while the file system is reading the file. When the read completes, the service provider notifies the client, for example, by calling a callback function provided by the client or sending an event notification to the client.
One example of a client/service provider model is the Transport Device Interface (“TDI”) of Microsoft Corporation. TDI defines the interactions between a “transport provider” that implements a transport layer and a “transport client” that uses the services of the transport provider. One example of a transport provider is a TCP driver, and one example of a transport client is a redirector of a file system. The redirector invokes functions provided by the TCP driver to send and receive messages via TCP. Thus, the redirector can access files stored on other computing devices using the services of the TCP driver.
A service provider may need to maintain state information for the client between requests received from the client. For example, when a file system opens a file on behalf of a client, the file system may need to maintain a file control block so that when the client requests to read from that file, it can locate the file control block needed to satisfy the request. Such information may be referred to as “context information.” One technique for tracking context information is to return a handle to the client. The handle may be an operating system object that contains or references context information for a service provider session. A service provider session may be delimited by an open and a close request, such as an open file and a close file request. The service provider provides the handle to the client at the beginning of the session, and the client provides the handle to the service provider with each request. This allows the service provider to locate context information that is appropriate for the session. For example, the TDI model specifies that a WINDOWS file object is used as the handle for a TDI session.
It is often desirable to be able to extend the functions provided by a service provider. To support such extension, many systems allow for requests for services made by clients to a service provider to be intercepted or hooked. When a client makes a request to a service provider, an interceptor may receive the request and perform additional processing before forwarding the request to the service provider. The interceptor may also intercept the response to the request and perform additional processing before forwarding the response to the client. FIG. 1 is a diagram that illustrates the layered architecture of client, interceptors, and service provider. When client 101 wants to request services of the service provider 102, the client invokes a function, which may pass control to one or more interceptors 111-119. When each interceptor is invoked, it performs its processing and then forwards the invocation to the next lower level interceptor. Ultimately, the lowest level interceptor invokes a function of the service provider.
Each interceptor, however, may need to maintain its context information. For example, an interceptor that simply logs to a file all invocations to a service provider during a session may have context information that includes the handle returned by the file system when the log file was opened or created. FIG. 2 is a diagram that illustrates one example of how interceptors track their context information. When client 210 requests a service of service provider 220, it is forwarded through interceptors 250 and 260 to the service provider. The service provider creates context information that is represented as a context object 221 (e.g., a file object). The service provider returns a reference to the context object to the lowest level interceptor, which forwards the references to the next higher level interceptor until the reference to the context object is eventually forwarded to the client. Each interceptor has its own context information 251 and 261. To track its context information, an interceptor may create a hash table 255 and 265 that contains references to its context information. The hash table is hashed based on the reference to the corresponding context object provided by the service. When the client makes another request of the service provider in the same service provider session, it includes the reference to the context object in its request. When an interceptor receives the request, the interceptor hashes the reference to locate its context information associated with that session. A difficulty with the use of a hash table by each interceptor is that there can be a high overhead in having to locate the appropriate context information for an interceptor.
FIG. 3 is a diagram that illustrates another example of how interceptors track their context information. In this example, service provider 320 creates context object 321 for a session of client 310. Each interceptor 350 and 360 also creates a context object 351 and 361. When the lowest level interceptor receives the response from the service provider, it replaces the reference to the context object of the service provider with a reference to its context object before forwarding the response to the next higher level interceptor. Eventually, the highest level interceptor forwards the response with a reference to its context object. Each interceptor includes as part of its context information the reference to the context object of the next lower layer. When the client requests a service of the service provider, it includes the reference provided by the highest-level interceptor, which is a reference to the context object for that interceptor. That interceptor uses the context information of the context object and then forwards the request to the next lower level interceptor using the reference provided by that next lower level interceptor. Eventually, the service provider receives a request that includes a reference to its context object. A difficulty with such an approach for tracking context information is that there can be a high overhead in creating context objects, especially if the creation of a context object requires a context switch from application mode to operating system mode.